#include "log.hpp"
#include "../encode/hex.h"
#include "../include/const.h"
#ifdef LINUX
#include <sys/syscall.h>
#endif

static const char* gs_pcaLevelStr[] = {"[TRACE]", "[DEBUG]", "[INFO] ", "[WARN] ", "[ERROR]", "[FATAL]", "[IMP]  "};

static CLog gs_clLog;

static FILE* gs_paPrint[] = {NULL, stdout, stdout, stderr, stderr, stderr, stdout};

CLog::~CLog()
{
    if(m_pstFile) fclose(m_pstFile);
}

CLog* CLog::GetIns() { return &gs_clLog; }

int CLog::Init(const char* pszFile, int iLogLevel, int iPrintLevel, int iSize)
{
    int iRet = -1;
    int iLen;
    vector<char> objPath;

    if(pszFile)
    {
        objPath.resize(PATH_LEN_MAX + 1);
        objPath[0] = 0;
        realpath(pszFile, objPath.data());

        m_pstFile = fopen(objPath.data(), "a");
        if(m_pstFile == NULL) SET_RET_AND_GOTO_EXIT(COMMON_ERR_FILE);

        m_strFile = string(objPath.data());

        iLen = strlen(objPath.data());
        if(iSize > 0 && iLen > 4)
        {
            iLen -= 4;
            if(strcmp(objPath.data() + iLen, ".log") == 0)
            {
                m_strPrefix = string(objPath.data(), iLen);
                m_u64Size   = iSize;
                m_u64Size *= (1024 * 1024);
            }
        }
    }

    m_iLogLevel   = iLogLevel;
    m_iPrintLevel = iPrintLevel;

    iRet = 0;
_Exit:
    return iRet;
}

void CLog::Log(int level, const char* file, int line, const char* fmt, ...)
{
    va_list pcVA;

    m_objLock.lock();

    va_start(pcVA, fmt);
    LogToFile(level, file, line, fmt, pcVA);
    va_end(pcVA);
    // 必须结束pcVA再重新开始，否则可能引发crash。此BUG只在x86_64-linux上出现过。
    va_start(pcVA, fmt);
    Print(level, fmt, pcVA);
    va_end(pcVA);

    m_objLock.unlock();
    return;
}

void CLog::LogToFile(int level, const char* file, int line, const char* fmt, va_list va)
{
    if(m_pstFile == NULL || level < m_iLogLevel) return;

    char caTmp[64];
    U64 u64Size;
    auto objTime  = STD_NOW;
    time_t stTime = std::chrono::system_clock::to_time_t(objTime);
    // time_t stTime      = time(NULL);
    struct tm* pstTime = localtime(&stTime);

    int iLen = strftime(caTmp, sizeof(caTmp), "%Y-%m-%d %H:%M:%S", pstTime);
    int iMS  = STD_MILLISECOND(objTime.time_since_epoch()) % 1000;
    sprintf(caTmp + iLen, ".%03d", iMS);
    // caTmp[iLen] = 0;

    fprintf(m_pstFile, "%s %s [%u] %s:%d: ", caTmp, gs_pcaLevelStr[level], ThrdID(), file, line);
    vfprintf(m_pstFile, fmt, va);
    fprintf(m_pstFile, "\n");
    fflush(m_pstFile);

    u64Size = ftell(m_pstFile);
    if(m_u64Size > 0 && u64Size >= m_u64Size)
    {
        fclose(m_pstFile);
        MoveLogFile();
        m_pstFile = fopen(m_strFile.c_str(), "a");
    }
    return;
}

void CLog::Print(int level, const char* fmt, va_list va)
{
    if(gs_paPrint[level] == NULL || level < m_iPrintLevel) return;

    char caTmp[64];
    time_t stTime      = time(NULL);
    struct tm* pstTime = localtime(&stTime);

    caTmp[strftime(caTmp, sizeof(caTmp), "%H:%M:%S", pstTime)] = '\0';
    fprintf(gs_paPrint[level], "%s %s ", caTmp, gs_pcaLevelStr[level]);
    vfprintf(gs_paPrint[level], fmt, va);
    fprintf(gs_paPrint[level], "\n");
    fflush(gs_paPrint[level]);

    return;
}

void CLog::HexLog(const char* file, int line, const char* pszName, const BYTE* data, int iLen)
{
    string strData;

    HexEncode(data, iLen, strData);

    Log(LOG_TRACE, file, line, "%s = %s", pszName, strData.c_str());

    return;
}

U32 CLog::ThrdID()
{
#ifdef WINDOWS
    return GetCurrentThreadId();
#endif
#ifdef LINUX
    return syscall(SYS_gettid);
#endif
}

int CLog::MoveLogFile()
{
    U32 u32High, u32Low;
    U64 u64Time;
    string strDst = m_strPrefix;
    auto t        = time(NULL);
    char caTmp[64];

    if(sizeof(t) >= 8)
    {
        u64Time = t;
        u32High = (u64Time >> 32);
        u32High %= 100;
        u32Low = (u64Time & CONST_U32_MAX);

        sprintf(caTmp, ".%02u.%u.log", u32High, u32Low);
    }
    else sprintf(caTmp, ".%d.log", (int)t);

    strDst += caTmp;

    rename(m_strFile.c_str(), strDst.c_str());
    return 0;
}
